import ConfigProvider from 'ant-design-vue/es/config-provider/index.js';
import Modal from 'ant-design-vue/es/modal/Modal.js';
import zhCN from 'ant-design-vue/es/locale/zh_CN.js';
import merge from 'lodash-es/merge.js';
import get from 'lodash-es/get.js';
import { useDraggable, useWindowSize, tryOnUnmounted } from '@vueuse/core';
import { defineAsyncComponent, nextTick, computed, isVNode, render, h } from 'vue';

import 'ant-design-vue/es/modal/style';
import vueGlobal from '@gis/lib/vueGlobal.js';

const visibleKey = 'open' in Modal.props ? 'open' : 'visible';
const dialogSize = { small: 600, medium: 900, large: 1200, max: 1500, maximum: 1800 };
const openedDialogs = [];

function useStyleTag(css, options = {}) {
  let { doc = document, id = 'style_tag_' + Date.now(), autoRemove = true } = Object.assign({}, options);
  let el = doc.getElementById(id);
  if (!el) {
    el = doc.createElement('style');
    el.id = id;
    doc.head.appendChild(el);
    if (autoRemove === true) {
      tryOnUnmounted(() => {
        doc.head.removeChild(el);
        css = options = doc = id = el = null;
      });
    }
  }
  if (el.textContent !== css) {
    el.textContent = css;
  }
}

function getUniId() {
  let i = ((Math.random() * 16) | 0).toString(16);
  let str = String(Math.random()).replace(/.*\./g, '-' + i + '-');
  return Date.now() + str;
}
function getParentEl(el, className) {
  if (el && el.parentElement) {
    if (el.parentElement.classList.contains(className)) {
      return el.parentElement;
    }
    let res = getParentEl(el.parentElement, className);
    if (res) return res;
  }
}
function getAppAttr(key, defaultValue) {
  return get(window.__GLOBAL_VUE_APP, key, defaultValue);
}
function isFunc(fn) {
  return typeof fn === 'function';
}
function isObj(obj) {
  return typeof obj === 'object' && obj;
}
function remove(arr, predicate) {
  for (let i = 0; i < arr.length; i++) {
    if (predicate(arr[i])) return arr.splice(i, 1);
  }
}
async function tap() {
  let obj, key, fn, params, defaultValue;
  if (isFunc(arguments[0])) {
    [fn, params, defaultValue] = arguments;
  } else {
    [obj, key, params, defaultValue] = arguments;
    fn = get(obj, key);
  }
  if (!isFunc(fn)) return defaultValue;
  try {
    return await fn.apply(null, params);
  } catch (e) {
    return false;
  }
}

function showAll() {
  openedDialogs.forEach((item) => tap(item, 'show'));
}
function hideAll() {
  openedDialogs.forEach((item) => tap(item, 'hide'));
}
function closeAll() {
  openedDialogs.forEach((item) => tap(item, 'close'));
  openedDialogs.length = 0;
}
function destroyAll() {
  closeAll();
}
function dialog({ content, contentProps, dialogProps }) {
  function destroy() {
    if (wrapVNode) {
      render(null, container);
      wrapVNode.component.update();
    }
    remove(openedDialogs, (item) => {
      return item.uid === uid;
    });
    if (openedDialogs && openedDialogs.length == 0) {
      vueGlobal.get('$vbus').$emit('dialogOpenOrClose', 'close');
    }
    nextTick(() => {
      wrapVNode = null;
      modalVNode = null;
      contentVNode = null;
      windowSize = null;
      modalRef = null;
      contentRef = null;
      container = null;
      content = null;
      contentProps = null;
      dialogProps = null;
      uid = null;
    });
  }
  function afterClose() {
    tap(dialogProps, 'afterClose');
    destroy();
  }
  async function close() {
    let res = await tap(contentRef, 'beforeClose');
    res = await tap(dialogProps, 'beforeClose', [res], res);
    if (res !== false) {
      update({ confirmLoading: false, [visibleKey]: false });
    }
  }
  async function cancel() {
    let res = await tap(contentRef, 'onCancel');
    if (res === false) {
      update({ confirmLoading: false });
      return;
    }
    res = await tap(dialogProps, 'onCancel');
    if (res === false) {
      update({ confirmLoading: false });
      return;
    }
    await close();
  }
  async function confirm() {
    update({ confirmLoading: true });
    let ok = await tap(contentRef, 'onOk');
    let res = await tap(dialogProps, 'onConfirm', [ok], ok);
    if (res !== undefined) ok = res;
    if (ok === false) {
      update({ confirmLoading: false });
      return;
    }
    res = await tap(dialogProps, 'onOk', [ok], ok);
    if (res !== undefined) ok = res;
    if (ok === false) {
      update({ confirmLoading: false });
      return;
    }
    await close();
  }
  function show() {
    update({ [visibleKey]: true });
  }
  function hide() {
    update({ [visibleKey]: false });
  }
  function update(updateProps) {
    if (modalVNode) {
      if (isFunc(updateProps)) {
        updateProps = updateProps(Object.assign({}, modalVNode.component.props));
      }
      if (typeof updateProps === 'object' && updateProps) {
        /* 不能让 modalVNode.component.props 失去响应式 */
        for (let key in updateProps) {
          modalVNode.component.props[key] = updateProps[key];
        }
        modalVNode.component.update();
      }
    }
  }
  function useMovable(contentRef) {
    if (get(dialogProps, 'movable') === true) {
      let modalEl = getParentEl(contentRef.$el, 'antd-dialog-modal');
      let headerEl = modalEl.querySelector('.ant-modal-header');
      useDraggable(headerEl, {
        onStart: (poi, e) => {
          // console.log('onStart', poi, e);
          document.body.style.cursor = 'move';
        },
        onMove: (poi, e) => {
          // console.log('onMove', poi, e);
          modalEl.style.margin = 0;
          modalEl.style.left = poi.x + 'px';
          modalEl.style.top = poi.y + 'px';
        },
        onEnd: (poi, e) => {
          // console.log('onEnd', poi, e);
          modalEl.style.left = poi.x + 'px';
          modalEl.style.top = poi.y + 'px';
          document.body.style.cursor = 'default';
        },
      });
    }
  }
  function handleSlots(slots, option, key, props) {
    let slot = option[key];
    if (slot === 'teleport') {
      delete option[key];
      let id = 'teleport_' + getUniId();
      props[key + 'Teleport'] = '#' + id;
      slots[key] = () => h('div', { id: id });
      return;
    }
    if (isVNode(slot) || isFunc(slot) || (isObj(slot) && isFunc(slot.render))) {
      delete option[key];
      slots[key] = (props) => {
        props = Object.assign({}, props, {
          confirmLoading: modalVNode.component.props.confirmLoading,
          onTrigger: (handle) => isFunc(handle) && handle(contentRef),
          onCloseDialog: close,
          onCancel: cancel,
          onOk: confirm,
        });
        return h(slot, props);
      };
    }
  }
  function handleFooterProp(props) {
    if (props.footer === true) {
      delete props.footer;
    }
    if (props.footer === false) {
      props.footer = null;
    }
  }
  function createContent({ content, cProps }) {
    cProps = Object.assign({}, cProps, {
      ref: 'contentRef',
      onCloseDialog: close,
      onVnodeMounted: () => {
        nextTick(() => {
          contentRef = contentVNode.ctx.refs.contentRef;
          useMovable(contentRef);
        });
      },
    });
    let contentSlot = cProps.slots;
    delete cProps.slots;
    contentVNode = h(content, cProps, contentSlot);
    return contentVNode;
  }
  function createModal({ content, cProps, dProps }) {
    // 大屏自适应，系统进行了比例的缩放
    let scale = Number(window.sessionStorage.getItem('systemScale') || '1');
    let spare = Number.isFinite(dProps.spare) ? dProps.spare : 300;
    let height = computed(() => {
      return (scale > 1 ? windowSize.height.value / scale : windowSize.height.value) - spare + 'px';
    });
    let width = dProps.width || dProps.size;
    let movable = get(dialogProps, 'movable') === true;
    let dialogStyle = movable ? { pointerEvents: 'none' } : {};
    let maskStyle = movable ? { pointerEvents: 'none' } : {};
    let bodyStyle = { boxSizing: 'border-box', overflow: 'auto' };
    if (/^(-|\d)\d*\.?\d+$/.test(dProps.height)) {
      bodyStyle.height = dProps.height + 'px';
    } else if (dProps.height === 'fixed') {
      bodyStyle.height = height.value;
    } else if (dProps.height) {
      bodyStyle.height = dProps.height;
    } else {
      bodyStyle.maxHeight = height.value;
    }
    let mProps = Object.assign({}, dProps, {
      [visibleKey]: true,
      ref: 'modalRef',
      closable: true,
      keyboard: false,
      maskClosable: false,
      destroyOnClose: true,
      confirmLoading: false,
      locale: zhCN,
      class: 'antd-dialog-modal',
      wrapClassName: 'antd-dialog-wrap',
      width: dialogSize[width] || width || dialogSize.small,
      style: Object.assign({ padding: 0 }, dProps.style),
      maskStyle: Object.assign({}, maskStyle, dProps.maskStyle),
      dialogStyle: Object.assign({}, dialogStyle, dProps.dialogStyle),
      bodyStyle: Object.assign({}, dProps.bodyStyle, bodyStyle),
      getContainer: () => getAppAttr('_container') || document.body,
      onCancel: cancel,
      onOk: confirm,
      afterClose: afterClose,
      onVnodeMounted: () => {
        nextTick(() => (modalRef = modalVNode.ctx.refs.modalRef));
      },
      onVnodeUnmounted: () => {
        nextTick(() => {
          content = cProps = dProps = scale = null;
        });
      },
    });
    let modalSlots = {
      default: () => h(createContent, { content, cProps }),
    };
    handleSlots(modalSlots, mProps, 'title', cProps);
    handleSlots(modalSlots, mProps, 'footer', cProps);
    handleFooterProp(mProps);
    modalVNode = h(Modal, mProps, modalSlots);
    // console.log('modalVNode: ', modalVNode);
    return modalVNode;
  }
  function createWrapper({ content, cProps, dProps }) {
    let config = {
      locale: zhCN,
      notUpdateGlobalConfig: true,
      dropdownMatchSelectWidth: true,
      getTargetContainer: () => {
        return contentRef && contentRef.$el && contentRef.$el.parentElement;
      },
      getPopupContainer: (node) => {
        return node && node.parentElement ? node.parentElement : contentRef && contentRef.$el;
      },
      onVnodeUnmounted: () => {
        nextTick(() => {
          content = cProps = dProps = null;
        });
      },
    };
    return h(ConfigProvider, config, () => {
      return h(createModal, { content, cProps, dProps });
    });
  }
  function createDialog(content, cProps, dProps) {
    wrapVNode = h(createWrapper, { content, cProps, dProps });
    wrapVNode.appContext = getAppAttr('_context') || wrapVNode.appContext;
    render(wrapVNode, container);
    vueGlobal.get('$vbus').$emit('dialogOpenOrClose', 'open');
    openedDialogs.push({ uid, show, hide, close });
  }

  useStyleTag(
    `
    .ant-modal-root .ant-modal-wrap .ant-modal .ant-modal-content {
      position: relative;
      padding: 0;
      background: var(--color-bg-container, #ffffff);
      border-radius: var(--border-radius, 6px);
      color: var(--color-text, rgba(0, 0, 0, 0.88));
      overflow: hidden;
    }
    .ant-modal-root .ant-modal-wrap .ant-modal .ant-modal-content .ant-modal-close {
      position: absolute;
      top: 0;
      right: 0;
      width: 56px;
      height: 56px;
      color: inherit;
    }
    .ant-modal-root .ant-modal-wrap .ant-modal .ant-modal-content .ant-modal-close .ant-modal-close-x {
      display: inline;
      width: unset;
      height: unset;
      line-height: 1;
    }
    .ant-modal-root .ant-modal-wrap .ant-modal .ant-modal-content .ant-modal-header {
      margin: 0;
      padding: 16px 80px 16px 24px;
      border-bottom: 1px solid var(--color-split, rgba(5, 5, 5, 0.06));
      background: var(--color-bg-container, #ffffff);
      color: inherit;
    }
    .ant-modal-root .ant-modal-wrap .ant-modal .ant-modal-content .ant-modal-header .ant-modal-title {
      color: inherit;
    }
    .ant-modal-root .ant-modal-wrap .ant-modal .ant-modal-content .ant-modal-body {
      padding: 24px;
    }
    .ant-modal-root .ant-modal-wrap .ant-modal .ant-modal-content .ant-modal-footer {
      margin: 0;
      padding: 16px 24px;
      border-top: 1px solid var(--color-split, rgba(5, 5, 5, 0.06));
    }
`,
    { id: 'antd_modal_style', autoRemove: false }
  );

  let container = document.createDocumentFragment();
  let windowSize = useWindowSize();
  let wrapVNode = null;
  let modalVNode = null;
  let contentVNode = null;
  let modalRef = null;
  let contentRef = null;
  let uid = getUniId();

  content = isFunc(content) ? defineAsyncComponent(content) : content;
  contentProps = Object.assign({}, contentProps);
  dialogProps = Object.assign({}, dialogProps);

  createDialog(content, contentProps, dialogProps);
  return { show, hide, close, update };
}

const promiseDialog = (options) => {
  return new Promise((resolve, reject) => {
    let footer = get(options, 'dialogProps.footer');
    let afterClose = get(options, 'dialogProps.afterClose');
    options = merge({}, options, {
      dialogProps: {
        onCancel: () => reject(false),
        onConfirm: (res) => res !== false && resolve(res),
        afterClose: () => {
          if (typeof afterClose === 'function') afterClose();
          footer === null || footer === false ? resolve() : reject();
          options = footer = afterClose = resolve = reject = null;
        },
      },
    });
    dialog(options);
  });
};

const Dialog = { open: dialog, showAll, hideAll, closeAll, destroyAll };

export { Modal, Dialog, dialog, promiseDialog };

export default {
  install(app) {
    try {
      Object.defineProperty(window, '__GLOBAL_VUE_APP', {
        value: app,
        enumerable: false,
        configurable: false,
        writable: false,
      });
    } catch (e) {
      console.log('Dialog: ', e);
    }
    Object.defineProperty(app.config.globalProperties, '$dialog', {
      enumerable: true,
      configurable: false,
      writable: false,
      value: dialog,
    });
    Object.defineProperty(app.config.globalProperties, '$promiseDialog', {
      enumerable: true,
      configurable: false,
      writable: false,
      value: promiseDialog,
    });
  },
};
